// Item (selectable) widget implmenentation for Gtk+
// Copyright 2006 (C) Ralph Thomas

#include <view/item.h>
#include <toolkit/selection.h>
#include <gtk/gtk.h>
#include <gtk/os_utilities.hpp>
#include <binder.hpp>

namespace view {
	typedef adobe::implementation::signal_wrapper_one_t<gboolean,GdkEventExpose*> paint_handler_t;
	typedef adobe::implementation::signal_wrapper_one_t<gboolean,GdkEventButton*> click_handler_t;
	//
	/// The item_state_t is used by the item and defines the
	/// internal state of an item widget. The generic_box uses
	/// box_state_t, which makes more use of data pumps (binders),
	/// etc.
	//
	struct item_state_t {
		adobe::auto_ptr<GtkWidget>	control_m;
		toolkit::selection_t::selected_state_t				selected_m;
		adobe::value_t			on_value_m;
		adobe::value_t			last_value_m;
		adobe::binder_helper_t		on_value_binder_m;
		adobe::binder_helper_t		value_binder_m;
		adobe::binder_ptr_t		double_click_action_m;
		adobe::binder_ptr_t		single_click_action_m;
		paint_handler_t			on_paint_m;
		click_handler_t			on_click_m;
		std::string				style_m;

		item_state_t( adobe::sheet_t& sheet, const adobe::value_t& on_value, const adobe::value_t& bind, const adobe::binder_ptr_t& on_double_click, const adobe::binder_ptr_t& on_single_click, const std::string& style )
		: selected_m( toolkit::selection_t::kUnselected ), on_value_binder_m( sheet ), value_binder_m( sheet ), double_click_action_m( on_double_click ), single_click_action_m( on_single_click ), style_m(style) {
			//
			// This widget is an event box. We draw directly into the
			// parent window. 
			//
			control_m.reset( gtk_event_box_new() );
			gtk_event_box_set_visible_window( GTK_EVENT_BOX(control_m.get()), false );
			gtk_widget_add_events( control_m.get(), GDK_ALL_EVENTS_MASK );
			GTK_WIDGET_SET_FLAGS( control_m.get(), GTK_CAN_FOCUS );
			//
			// Initialize all of our logic.
			//
			on_value_binder_m.initialize( on_value, boost::bind( &item_state_t::update_value, this, _1 ), 0 );
			value_binder_m.initialize( bind, boost::bind( &item_state_t::update_state, this, _1 ), 0 );
		}
		~item_state_t()
		{
		}
		void init() {
			on_paint_m.set_callback( boost::bind( &item_state_t::paint, this, _1 ) );
			on_click_m.set_callback( boost::bind( &item_state_t::click, this, _1 ) );
			on_paint_m.connect( control_m.get(), "expose-event", true );
			on_click_m.connect( control_m.get(), "button-press-event" );
		}

		void update_selection( toolkit::selection_t::selected_state_t s )
		{
			if( s == selected_m ) return;
			selected_m = s;
			if (s == toolkit::selection_t::kSelected)
				gtk_widget_set_state( control_m.get(), (GtkStateType)(GTK_WIDGET_STATE(control_m.get()) | GTK_STATE_SELECTED) );
			else
				gtk_widget_set_state( control_m.get(), (GtkStateType)(GTK_WIDGET_STATE(control_m.get()) & ~GTK_STATE_SELECTED) );
			gtk_widget_queue_draw( control_m.get() );
		}

		void update_state( const adobe::value_t& val ) {
			if( val == on_value_m )
				update_selection( toolkit::selection_t::kSelected );
			else
				update_selection( toolkit::selection_t::kUnselected );
			last_value_m = val;
		}

		void update_value( const adobe::value_t& val ) {
			if( val == on_value_m ) return;
			on_value_m = val;
		}
		
		gboolean paint( GdkEventExpose* expose ) {
			if( selected_m == toolkit::selection_t::kUnselected ) return false;
			GtkWidget* w( control_m.get() );

			GdkRectangle clip;
			clip.x = w->allocation.x; clip.y = w->allocation.y;
			clip.width = w->allocation.width; clip.height = w->allocation.height;
			gtk_paint_box( w->style, w->window, (GtkStateType)GTK_WIDGET_STATE(w), GTK_SHADOW_NONE, &clip, w, "IconView", clip.x, clip.y, clip.width, clip.height );
			if (GTK_WIDGET_HAS_FOCUS(w))
				gtk_paint_focus( w->style, w->window, (GtkStateType)GTK_WIDGET_STATE(w), &clip, w, "IconView", clip.x + 3, clip.y + 3, clip.width - 6, clip.height - 6 );
			return false;
		}
		gboolean click( GdkEventButton* button ) {
			if( button->type == GDK_2BUTTON_PRESS ) {
				if( double_click_action_m ) double_click_action_m->invoke();
				return true;
			}
			gtk_widget_grab_focus(control_m.get());
			value_binder_m.set( on_value_m );
			if( single_click_action_m ) single_click_action_m->invoke();
			//
			// If we're already selected then don't bother selecting
			// ourselves again. Let a child handle this message.
			//
			if (last_value_m == on_value_m) return false;
			//
			// Our own value_binder debounces this message and doesn't repeat
			// what we just told it back. We do this ourselves here.
			//
			update_state( on_value_m );
			return true;
		}
	};
	adobe::widget_node_t item_factory(
		const adobe::dictionary_t& 	parameters,
		const adobe::widget_node_t&	parent,
		const adobe::factory_token_t&	token )
	{
		//
		// Extract all of the mandatory items from the dictionary.
		//
		adobe::value_t bind = parameters.at( adobe::static_name_t( "bind" ) );
		adobe::value_t on_value = parameters.at( adobe::static_name_t( "on_value" ) );
		//
		// Optional ones..
		//
		adobe::binder_ptr_t double_click_action;
		adobe::binder_ptr_t single_click_action;
		std::string style;
		try {
			parameters.get( adobe::static_name_t( "action" ), double_click_action );
			parameters.get( adobe::static_name_t( "single_click" ), single_click_action );
			parameters.get( adobe::static_name_t( "style" ), style );
		} catch( ... ) {}
		//
		// Create the widget. We also create a child fixed layout
		// widget and embed it inside our item. We do this so that
		// children can get properly laid out.
		//
		item_state_t* item = new item_state_t( token.sheet_m, on_value, bind, double_click_action, single_click_action, style );
		GtkWidget* item_widget = item->control_m.get();
		if( !style.empty() )
			gtk_widget_set_name( item_widget, style.c_str() );
		GtkWidget* fixed = gtk_fixed_new();
		token.assemblage_m.delete_on_destruction( item );
		token.assemblage_m.delete_on_destruction( fixed );
		//
		// Insert this widget into it's parent.
		//
		adobe::display_t::position_t display_token( adobe::insert( adobe::get_main_display(), parent.display_token_m, item_widget ) );
		adobe::display_t::position_t overlay_token( token.overlay_m.insert( parent.overlay_token_m ) );
		//
		// Insert the fixed widget into the item widget.
		//
		display_token = adobe::insert( adobe::get_main_display(), display_token, fixed );
		//
		// Bind the calculate and place signals for Eve.
		//
		adobe::eve_t::signal_suite_t signals;
		adobe::extents_t extents;
		eve_client::place_func_t placer = boost::bind( &adobe::implementation::set_bounds_widget, item_widget, _1, _2 );
		//
		// This constant extent is wrong. It should say that
		// there's a frame of 3x3 or something similar.
		//
		extents.width() = extents.height() = 6;
		signals.eve_calculate_proc_m = boost::bind( &eve_client::utility_widget_calculate, extents, _1 );
		signals.eve_place_proc_m = boost::bind( &eve_client::utility_widget_place, placer, _1, _2, boost::ref( token.overlay_m ), overlay_token );
		//
		// We're a container so can't use the default calculate_data_t.
		//
		adobe::eve_t::calculate_data_t cdata;
		cdata.horizontal().length_m = 10;
		cdata.vertical().length_m = 10;
		cdata.horizontal().margin_m.first = 5;
		cdata.vertical().margin_m.first = 5;
		cdata.horizontal().margin_m.second = 5;
		cdata.vertical().margin_m.second = 5;
		cdata.placement_m = adobe::eve_t::place_row;
		//
		// Register the widget with Eve.
		//
		adobe::eve_t::iterator eve_token(
			token.layout_m.add_view_element( parent.eve_token_m,
				adobe::eve_t::insert_element_t(
					cdata,
					true,
					parameters,
					signals )
				)
			);
		item->init();
		return adobe::widget_node_t( parent.size_m, eve_token, display_token, overlay_token );
	}
}
